package negocios;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import negocios.excecoes.EntradaInvalidaException;
import negocios.excecoes.NaoEncontradoException;
import negocios.excecoes.SessaoInexistenteException;
import negocios.excecoes.SistemaException;

import util.Criptografia;

import bd.FacadeBD;

/**
 * A classe principal do Sistema I'm Here, contendo todas as operacoes
 * disponiveis no User Stories. Implementa o padrao Singleton.
 * 
 * @author Jonathan Brilhante
 * @author Jose Rafael
 * @author Nata Venancio
 * @author Renato Almeida
 * 
 * @version 2.0
 */

public class Sistema {
	/**
	 * O email do sistema I'm Here, usado para enviar convites
	 */
	public static final String EMAIL_SISTEMA = "iam@email.com";

	private static Sistema instance;
	private static ColecaoUsuarios usuarios;
	private static List<Chat> chatsAbertos;

	/**
	 * Construtor privado do sistema
	 */
	private Sistema() {
		chatsAbertos = new ArrayList<Chat>();
		try {
			usuarios = FacadeBD.getInstance().getUsuarios();
		} catch (Exception e) {
			throw new SistemaException();
		}
	}

	/**
	 * Retorna a instancia unica do sistema
	 * 
	 * @return a instancia unica do sistema
	 */
	public static synchronized Sistema getInstance() {
		if (instance == null) {
			instance = new Sistema();
		}
		return instance;
	}

	/**
	 * Busca um usuario pelo seu nome, login, email ou telefone. A pesquisa sera
	 * feita baseada snome, ou login, telefone, e email, sao iguais a substring
	 * passada (use uma string vazia "" para ignorar o parametro)
	 * 
	 * @param nome
	 *            o nome do usuario a pesquisar
	 * @param login
	 *            o login do usuario a pesquisar
	 * @param email
	 *            o email do usuario a pesquisar
	 * @param telefone
	 *            o telefone do usuario a pesquisar
	 * @return o usuario pesquisado
	 * @throws NaoEncontradoException
	 *             caso o usuario nao seja encontrado pelos parametros de
	 *             pesquisa
	 */
	public Usuario buscar(String nome, String login, String email,
			String telefone) throws NaoEncontradoException {
		Usuario us = usuarios.buscar(nome, login, email, telefone);
		if (us != null)
			return us;
		throw new NaoEncontradoException("O usuario nao existe.");
	}

	/**
	 * Retorna um conjunto de usuarios com o nome comecando com a substring
	 * passada como parametro
	 * 
	 * @param prefixo
	 *            o prefixo dos nomes dos usuarios a buscar
	 * @return um conjunto de nomes ordenado em ordem crescente
	 */
	public List<Usuario> usuariosPorNome(String prefixo) {
		return usuarios.usuariosPorNome(prefixo);
	}

	/**
	 * Loga um Usuario no sistema, baseado em seu login e senha
	 * 
	 * @param login
	 *            o login do usuario
	 * @param senha
	 *            a senha do usuario
	 * @return o Usuario logado
	 * @throws NaoEncontradoException
	 *             caso o usuario nao seja encontrado
	 */
	public Usuario autenticar(String login, String senha)
			throws NaoEncontradoException {
		Usuario us = usuarios.buscaPorLogin(login, senha);
		if (us == null)
			throw new NaoEncontradoException("Login/senha invalidos.");
		us.logar();
		return us;
	}

	/**
	 * Desloga um usuario do sistema
	 * 
	 * @param login
	 *            o login do usuario a ser deslogado pelo sistem
	 * @throws SessaoInexistenteException
	 *             caso a sessao desse usuario ja tenha expirado, ou ele nao
	 *             exista
	 */
	public void deslogar(String login) throws SessaoInexistenteException {
		Usuario us = usuarios.buscar("", login, "", "");
		if (us == null || !us.isLogado()) {
			throw new SessaoInexistenteException();
		}
		us.deslogar();
	}

	/**
	 * Cria um novo UsuarioComum a partir dos dados passados como parametro.
	 * 
	 * @param login
	 *            o login do usuario
	 * @param nome
	 *            o nome do usuario
	 * @param email
	 *            o email do usuario
	 * @param senha
	 *            a senha do usuario
	 * @param telefone
	 *            o telefone do usuario
	 * @throws EntradaInvalidaException
	 *             caso algum dado seja invalido
	 */
	public void criarUsuario(String login, String nome, String email,
			String senha, String telefone) throws EntradaInvalidaException {
		Usuario novo = new UsuarioComum(login, nome, email, Criptografia
				.cifra(senha), telefone);
		if (usuarios.contains(novo)) {
			throw new EntradaInvalidaException("O username jah existe.");
		}
		usuarios.add(novo);
	}

	/**
	 * Deleta um usuario do sistema, caso login e senha batam com o esperado.
	 * 
	 * @param login
	 *            o login do usuario deletado
	 * @param senha
	 *            a senha do usuario deletado, por questao de seguranca
	 * @throws NaoEncontradoException
	 *             caso o usuario a deletar nao seja encontrado
	 */
	public void deletarUsuario(String login, String senha)
			throws NaoEncontradoException {
		Usuario delete = usuarios.buscaPorLogin(login, senha);
		if (delete == null) {
			throw new NaoEncontradoException(login);
		}
		for (Relacionamento a : delete.amigos()) { // apaga referencias
			a.getAmigo().excluirAmigo(delete);
		}
		usuarios.remove(delete);
	}

	/**
	 * Envia um email ao endereco passado como parametro
	 * 
	 * @param receptor
	 *            o receptor do email
	 * @throws IOException
	 *             caso nao seja possivel acessar o historico do sistema
	 */
	public void enviarEmail(Usuario receptor, MensagemTexto email)
			throws IOException {
		receptor.getCaixaPostal().inserir(email);
		FacadeBD.getInstance().logEmail(email);
	}

	/**
	 * Envia um SMS ao endereco passado como parametro
	 * 
	 * @param receptor
	 *            o receptor do SMS
	 * @throws IOException
	 *             caso nao seja possivel acessar o historico do sistema
	 */
	public void enviarSMS(Usuario receptor, SMS sms) throws IOException {
		receptor.getCaixaPostal().inserir(sms);
		FacadeBD.getInstance().logSMS(sms);
	}

	/**
	 * Envia um convite de relacionamento I'm Here para um usuario
	 * 
	 * @param receptor
	 *            o receptor do convite
	 * @param convite
	 *            o convite recebido
	 * @throws IOException
	 *             caso nao seja possivel acessar o historico do sistema
	 */
	public void enviarConvite(Usuario receptor, Convite convite)
			throws IOException {
		if (receptor != null) {
			receptor.getCaixaPostal().inserir(convite);
		}
		FacadeBD.getInstance().logConvite(convite);
	}

	/**
	 * Inicia um chat entre dois usuarios
	 * 
	 * @param us1
	 *            o primeiro usuario
	 * @param us2
	 *            o segundo usuario
	 * 
	 * @throws NaoEncontradoException
	 *             caso o usuario nao seja encontrado
	 */
	public void iniciarChat(Usuario us1, Usuario us2)
			throws NaoEncontradoException {
		chatsAbertos.add(new Chat(us1, us2));
	}

	/**
	 * Envia uma mensagem de chat para o destinatario
	 * 
	 * @param rem
	 *            o remetente da mensagem
	 * @param des
	 *            o destinatario da mensagem
	 * @param texto
	 *            a mensagem
	 */
	public void enviarMensagem(Usuario rem, Usuario des, String texto) {
		Chat c = getChat(rem, des);
		if (c == null) {
			return;
		}
		c.enviarMensagem(des, texto);
	}

	/**
	 * Retorna a ultima mensagem, do remetente para o destinatario
	 * 
	 * @param rem
	 *            quem enviou a mensagem
	 * @param des
	 *            o destinatario da mensagem
	 * @return a mensagem
	 */
	public String receberMensagem(Usuario rem, Usuario des) {
		Chat c = getChat(rem, des);
		if (c == null) {
			return null;
		}
		return c.receberMensagem(rem);
	}

	/**
	 * Encerra o chat entre os dois usuarios. Caso nao exista, nao faz nada.
	 * Apos encerrado, o chat e guardado no historico do sistema
	 * 
	 * @param us1
	 *            o usuario 1
	 * @param us2
	 *            o usuario 2
	 * @throws IOException
	 *             caso nao seja possivel fazer o log do chat no historico
	 */
	public void encerrarChat(Usuario us1, Usuario us2) throws IOException {
		Chat c = getChat(us1, us2);
		if (c == null) {
			return;
		}
		chatsAbertos.remove(c);
		FacadeBD.getInstance().logChat(c);
	}

	/**
	 * Metodo que sera chamado quando houver suporte a web, e o usuario puder
	 * clicar no link de compartilhamento dos convites
	 * 
	 * @param us1
	 *            o usuario convidado
	 * @param us2
	 *            o segundo, usuario que enviou o convite
	 * @param oculto
	 *            o modo de visibilidade do compartilhamento
	 * @throws EntradaInvalidaException
	 *             caso o usuario ja seja amigo
	 */
	public void confirmarCompartilhamento(Usuario us1, Usuario us2,
			boolean oculto) throws EntradaInvalidaException {
		us1.adicionaAmigo(us2, oculto);
		us2.adicionaAmigo(us1);
	}

	/**
	 * Encerra o sistema, mas antes salva todas as informacoes dos usuarios no
	 * banco de dados
	 */
	public void sair() {
		FacadeBD.getInstance().salvaModificacoes(usuarios);
	}

	/**
	 * Reseta a lista de usuarios e o historico, apagando tudo dos bancos de
	 * dados
	 * 
	 * @throws IOException
	 *             caso nao seja possivel acessar o historico
	 */
	public void reset() throws IOException {
		usuarios.reset();
		sair();
	}

	/**
	 * Metodo auxiliar para encontrar chats abertos do sistema
	 * 
	 * @param us1
	 *            o primeiro usuario
	 * @param us2
	 *            o segundo usuario
	 * @return o chat, se existir, null, caso contrario
	 */
	private Chat getChat(Usuario us1, Usuario us2) {
		for (Chat c : chatsAbertos) {
			if ((c.getUsuario1().equals(us1) && c.getUsuario2().equals(us2))
					|| (c.getUsuario1().equals(us2) && c.getUsuario2().equals(
							us1))) {
				return c;
			}
		}
		return null;
	}
}